# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.

package Bugzilla::Field::ChoiceInterface;

use 5.14.0;
use strict;
use warnings;

use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::Field;

use Scalar::Util qw(blessed);

# Helps implement the "field" accessor without subclasses having to
# write code.
sub FIELD_NAME { return $_[0]->DB_TABLE; }

####################
# Subclass Helpers #
####################

sub _check_if_controller {
  my $self       = shift;
  my $vis_fields = $self->controls_visibility_of_fields;
  my $values     = $self->controlled_values_array;
  if (@$vis_fields || @$values) {
    ThrowUserError(
      'fieldvalue_is_controller',
      {
        value  => $self,
        fields => [map($_->name, @$vis_fields)],
        vals   => $self->controlled_values
      }
    );
  }
}


#############
# Accessors #
#############

sub is_active { return $_[0]->{'isactive'}; }
sub sortkey   { return $_[0]->{'sortkey'}; }

sub bug_count {
  my $self = shift;
  return $self->{bug_count} if defined $self->{bug_count};
  my $dbh   = Bugzilla->dbh;
  my $fname = $self->field->name;
  my $count;
  if ($self->field->type == FIELD_TYPE_MULTI_SELECT) {
    $count = $dbh->selectrow_array(
      "SELECT COUNT(*) FROM bug_$fname
                                         WHERE value = ?", undef, $self->name
    );
  }
  else {
    $count = $dbh->selectrow_array(
      "SELECT COUNT(*) FROM bugs 
                                         WHERE $fname = ?", undef, $self->name
    );
  }
  $self->{bug_count} = $count;
  return $count;
}

sub field {
  my $invocant = shift;
  my $class    = ref $invocant || $invocant;
  my $cache    = Bugzilla->request_cache;

  # This is just to make life easier for subclasses. Our auto-generated
  # subclasses from Bugzilla::Field::Choice->type() already have this set.
  $cache->{"field_$class"} ||= new Bugzilla::Field({name => $class->FIELD_NAME});
  return $cache->{"field_$class"};
}

sub is_default {
  my $self = shift;
  my $name = $self->DEFAULT_MAP->{$self->field->name};

  # If it doesn't exist in DEFAULT_MAP, then there is no parameter
  # related to this field.
  return 0 unless $name;
  return ($self->name eq Bugzilla->params->{$name}) ? 1 : 0;
}

sub is_static {
  my $self = shift;

  # If we need to special-case Resolution for *anything* else, it should
  # get its own subclass.
  if ($self->field->name eq 'resolution') {
    return $self->name eq '' || $self->name eq 'DUPLICATE';
  }
  elsif ($self->field->custom) {
    return $self->name eq '---' ? 1 : 0;
  }
  return 0;
}

sub controls_visibility_of_fields {
  my $self = shift;
  my $dbh  = Bugzilla->dbh;

  if (!$self->{controls_visibility_of_fields}) {
    my $ids = $dbh->selectcol_arrayref(
      "SELECT id FROM fielddefs
               INNER JOIN field_visibility
                 ON fielddefs.id = field_visibility.field_id
             WHERE value_id = ? AND visibility_field_id = ?", undef, $self->id,
      $self->field->id
    );

    $self->{controls_visibility_of_fields} = Bugzilla::Field->new_from_list($ids);
  }

  return $self->{controls_visibility_of_fields};
}

sub visibility_value {
  my $self = shift;
  if ($self->{visibility_value_id}) {
    require Bugzilla::Field::Choice;
    $self->{visibility_value}
      ||= Bugzilla::Field::Choice->type($self->field->value_field)
      ->new($self->{visibility_value_id});
  }
  return $self->{visibility_value};
}

sub controlled_values {
  my $self = shift;
  return $self->{controlled_values} if defined $self->{controlled_values};
  my $fields = $self->field->controls_values_of;
  my %controlled_values;
  require Bugzilla::Field::Choice;
  foreach my $field (@$fields) {
    $controlled_values{$field->name} = Bugzilla::Field::Choice->type($field)
      ->match({visibility_value_id => $self->id});
  }
  $self->{controlled_values} = \%controlled_values;
  return $self->{controlled_values};
}

sub controlled_values_array {
  my ($self) = @_;
  my $values = $self->controlled_values;
  return [map { @{$values->{$_}} } keys %$values];
}

sub is_visible_on_bug {
  my ($self, $bug) = @_;

  # Values currently set on the bug are always shown.
  return 1 if $self->is_set_on_bug($bug);

  # Inactive values are, otherwise, never shown.
  return 0 if !$self->is_active;

  # Values without a visibility value are, otherwise, always shown.
  my $visibility_value = $self->visibility_value;
  return 1 if !$visibility_value;

  # Values with a visibility value are only shown if the visibility
  # value is set on the bug.
  return $visibility_value->is_set_on_bug($bug);
}

sub is_set_on_bug {
  my ($self, $bug) = @_;
  my $field_name = $self->FIELD_NAME;

  # This allows bug/create/create.html.tmpl to pass in a hashref that
  # looks like a bug object.
  my $value = blessed($bug) ? $bug->$field_name : $bug->{$field_name};
  $value = $value->name if blessed($value);
  return 0 if !defined $value;

  if ( $self->field->type == FIELD_TYPE_BUG_URLS
    or $self->field->type == FIELD_TYPE_MULTI_SELECT)
  {
    return grep($_ eq $self->name, @$value) ? 1 : 0;
  }
  return $value eq $self->name ? 1 : 0;
}

1;

__END__

=head1 NAME

Bugzilla::Field::ChoiceInterface - Makes an object act like a
Bugzilla::Field::Choice.

=head1 DESCRIPTION

This is an "interface", in the Java sense (sometimes called a "Role"
or a "Mixin" in other languages). L<Bugzilla::Field::Choice> is the
primary implementor of this interface, but other classes also implement
it if they want to "act like" L<Bugzilla::Field::Choice>.

=head1 METHODS

=head2 Accessors

These are in addition to the standard L<Bugzilla::Object> accessors.

=over

=item C<sortkey>

The key that determines the sort order of this item.

=item C<field>

The L<Bugzilla::Field> object that this field value belongs to.

=item C<is_active>

Whether or not this value should appear as an option on bugs that do
not already have it set as the current value.

=item C<is_static>

C<0> if this field value can be renamed or deleted, C<1> otherwise.

=item C<is_default>

C<1> if this is the default value for this field, C<0> otherwise.

=item C<bug_count>

An integer count of the number of bugs that have this value set.

=item C<controls_visibility_of_fields>

Returns an arrayref of L<Bugzilla::Field> objects, representing any
fields whose visibility are controlled by this field value.

=item C<controlled_values>

Tells you which values in B<other> fields appear (become visible) when this
value is set in its field.

Returns a hashref of arrayrefs. The hash keys are the names of fields,
and the values are arrays of objects that implement
C<Bugzilla::Field::ChoiceInterface>, representing values that this value 
controls the visibility of, for that field.

=item C<visibility_value>

Returns an object that implements C<Bugzilla::Field::ChoiceInterface>,
which represents the value that needs to be set in order for this
value to appear in the UI.

=item C<is_visible_on_bug>

Returns C<1> if, according to the settings of C<is_active> and 
C<visibility_value>, this value should be displayed as an option
when viewing a bug. Returns C<0> otherwise.

Takes a single argument, a L<Bugzilla::Bug> object or a hash with
similar fields to a L<Bugzilla::Bug> object.

=item C<is_set_on_bug>

Returns C<1> if this value is the current value set for its field on
the passed-in L<Bugzilla::Bug> object (or a hash that looks like a
L<Bugzilla::Bug>). For multi-valued fields, we return C<1> if
I<any> of the currently selected values are this value.

Returns C<0> otherwise.

=back

=head1 B<Methods in need of POD>

=over

=item FIELD_NAME

=item controlled_values_array

=back
